package solvoku.model.game;

/**
 *
 * @author Steve Cancès <steve.cances@gmail.com>
 */
public class Grille {

    private final Case[][] cases;
    private final GroupeCase[] colonnes;
    private final GroupeCase[] lignes;
    private final GroupeCase[] blocs;

    private CoupAJouer dernierCoupAJouer;
    private boolean grilleOk;

    public Grille() {
        this.cases = new Case[9][9];
        this.colonnes = new GroupeCase[9];
        this.lignes = new GroupeCase[9];
        this.blocs = new GroupeCase[9];
        this.dernierCoupAJouer = null;
        this.grilleOk = true;

        for (int i = 0; i < 9; ++i) {
            this.colonnes[i] = new GroupeCase(this);
            this.lignes[i] = new GroupeCase(this);
            this.blocs[i] = new GroupeCase(this);
        }

        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 9; ++y) {
                int numBloc = x / 3 + (y / 3) * 3;
                Case c = new Case(x, y);
                this.cases[x][y] = c;
                this.lignes[y].addCase(c);
                this.colonnes[x].addCase(c);
                this.blocs[numBloc].addCase(c);
                c.setLigne(this.lignes[y]);
                c.setColonne(this.colonnes[x]);
                c.setBloc(this.blocs[numBloc]);
                //c.setGrille(this);
            }
        }
    }

    public Grille(Integer[][] values) {
        this();
        this.init(values);
    }

    public Grille(Grille grille) {
        this();
        Integer[][] values = new Integer[9][9];
        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 9; ++y) {
                values[x][y] = grille.cases[x][y].getValue();
            }
        }
        this.init(values);
    }

    protected final void init(Integer[][] values) {
        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 9; ++y) {
                Case c = this.cases[x][y];
                int value = values[x][y];
                c.setValue(value);
                c.getLigne().majPossibilitesCase(value);
                c.getColonne().majPossibilitesCase(value);
                c.getBloc().majPossibilitesCase(value);
            }
        }
    }

    public Case[][] getCases() {
        return cases;
    }

    public GroupeCase[] getColonnes() {
        return colonnes;
    }

    public GroupeCase[] getLignes() {
        return lignes;
    }

    public GroupeCase[] getBlocs() {
        return blocs;
    }

    public CoupAJouer getDernierCoupAJouer() {
        return dernierCoupAJouer;
    }

    protected CoupAJouer getCoupAJouerResolutionSimple() {
        CoupAJouer coupAJouer = this.getCoupAJouerSimple();
        if (coupAJouer == null) {
            coupAJouer = this.getCoupAJouerUneSeulePossibilite();
            if (coupAJouer == null) {
                coupAJouer = this.getCoupAJouerPossibiliteUniqueDansGroupe();
            }
        }
        return coupAJouer;
    }

    public CoupAJouer getCoupAJouerResolutionComplex() {
        CoupAJouer coupAJouer = this.getCoupAJouerResolutionSimple();
        if (coupAJouer == null) {
            Grille g = new Grille(this);
            g.resoudreSiPossible();
            CoupAJouer dernierCoupAJouerDeG = g.getDernierCoupAJouer();
            if (dernierCoupAJouerDeG != null) {
                coupAJouer = new CoupAJouer(this.cases[dernierCoupAJouerDeG.getC().getX()][dernierCoupAJouerDeG.getC().getY()], dernierCoupAJouerDeG.getValue());
            }
        }
        return coupAJouer;
    }

    protected CoupAJouer getCoupAJouerSimple(GroupeCase[] groupeCases) {
        CoupAJouer coupAJouer = null;
        for (GroupeCase groupeCase : groupeCases) {
            coupAJouer = groupeCase.getCoupSimpleAJouer();
            if (coupAJouer != null) {
                return coupAJouer;
            }
        }
        return coupAJouer;
    }

    protected CoupAJouer getCoupAJouerSimple() {
        CoupAJouer coupAJouer = this.getCoupAJouerSimple(this.lignes);
        if (coupAJouer == null) {
            coupAJouer = this.getCoupAJouerSimple(this.colonnes);
            if (coupAJouer == null) {
                coupAJouer = this.getCoupAJouerSimple(this.blocs);
            }
        }
        return coupAJouer;
    }

    protected CoupAJouer getCoupAJouerUneSeulePossibilite() {
        CoupAJouer coupAJouer = null;
        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 9; ++y) {
                Case c = this.cases[x][y];
                if (c.isVide()) {
                    if (c.nbPossibilites() == 1) {
                        coupAJouer = new CoupAJouer(c, (int) c.getPossibilites().toArray()[0]);
                        return coupAJouer;
                    }
                }
            }
        }
        return coupAJouer;
    }

    protected CoupAJouer getCoupAJouerPossibiliteUniqueDansGroupe(GroupeCase[] groupeCases) {
        CoupAJouer coupAJouer = null;
        for (GroupeCase groupeCase : groupeCases) {
            coupAJouer = groupeCase.getCoupAJouerPossibiliteUniqueDansGroupe();
            if (coupAJouer != null) {
                return coupAJouer;
            }
        }
        return coupAJouer;
    }

    protected CoupAJouer getCoupAJouerPossibiliteUniqueDansGroupe() {
        CoupAJouer coupAJouer = this.getCoupAJouerPossibiliteUniqueDansGroupe(this.lignes);
        if (coupAJouer == null) {
            coupAJouer = this.getCoupAJouerPossibiliteUniqueDansGroupe(this.colonnes);
            if (coupAJouer == null) {
                coupAJouer = this.getCoupAJouerPossibiliteUniqueDansGroupe(this.blocs);
            }
        }
        return coupAJouer;
    }

    protected boolean isResolue() {
        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 9; ++y) {
                if (this.cases[x][y].isVide()) {
                    return false;
                }
            }
        }
        return true;
    }

    protected boolean resoudreSimple() {
        CoupAJouer coupAJouer;
        do {
            coupAJouer = this.getCoupAJouerResolutionSimple();
            if (coupAJouer != null) {
                boolean okJouerCoup = coupAJouer.jouerCoup();
                if(!okJouerCoup){
                    this.grilleOk = false;
                    return false;
                }
            }
        } while (coupAJouer != null);
        return this.isResolue();
    }

    protected boolean resoudreSiPossible() {
        if (!this.resoudreSimple() && this.grilleOk) {
            Case caseToTest = null;
            int nbPossibilitesCaseToTest = 10;
            for (int x = 0; x < 9; ++x) {
                for (int y = 0; y < 9; ++y) {
                    Case c = this.cases[x][y];
                    if (c.isVide()) {
                        if (c.nbPossibilites() < nbPossibilitesCaseToTest) {
                            caseToTest = c;
                            nbPossibilitesCaseToTest = c.nbPossibilites();
                        }
                    }
                }
            }
            if (caseToTest != null) {
                for (Integer possibilite : caseToTest.getPossibilites()) {
                    Grille g = new Grille(this);
                    this.dernierCoupAJouer = new CoupAJouer(g.getCases()[caseToTest.getX()][caseToTest.getY()], possibilite);
                    this.dernierCoupAJouer.jouerCoup();
                    if (g.resoudreSiPossible()) {
                        return true;
                    }
                }
                return false;
            } else {
                return false;
            }
        }
        return this.grilleOk;
    }
    
    public boolean resoudre() {
        CoupAJouer coupAJouer;
        do {
            coupAJouer = this.getCoupAJouerResolutionComplex();
            if (coupAJouer != null) {
                coupAJouer.jouerCoup();
            }
        } while (coupAJouer != null);
        return this.grilleOk;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        int oldNumBlocX = 0;
        for (int x = 0; x < 9; ++x) {
            int oldNumBlocY = 0;
            for (int y = 0; y < 9; ++y) {
                int numBlocX = x / 3;
                int numBlocY = (y / 3) * 3;
                if (numBlocX != oldNumBlocX) {
                    stringBuilder.append("- - - - - - - - - - -\n");
                }
                if (numBlocY != oldNumBlocY) {
                    stringBuilder.append("| ");
                }
                oldNumBlocX = numBlocX;
                oldNumBlocY = numBlocY;
                stringBuilder.append(this.cases[x][y].getValue()).append(' ');
            }
            stringBuilder.append('\n');
        }
        return stringBuilder.toString();
    }

}
